/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: system_property_impl.cpp
 *
 * Purpose: system property implementation.
 *
 * Developer:
 *   wen.gu , 2019-10-05
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "panda/sys/internal/system_property_impl.h"

#include <string.h>

#include "panda/core/shared_memory.h"

namespace panda
{
namespace sys
{
namespace internal_impl
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/

#define PROP_ALIGN(__value, __alignment) (((__value) + (__alignment)-1) & ~((__alignment)-1))

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

constexpr uint32_t gPropTrieMagic = 0x5afe5afe;
constexpr uint32_t gPropTrieVersion = 0xefef01fe;

uint32_t PropTrie::mPropAreaSize = 0;
uint32_t PropTrie::mPropAreaDataSize = 0;

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static int PropNameCmp(const char* one, uint32_t one_len, 
                       const char* two, uint32_t two_len) 
{
    if (one_len < two_len)
    {
         return -1;
    }        
    else if (one_len > two_len)
    {
         return 1;
    }         
    else
    {
       return strncmp(one, two, one_len);
    }   
}


//static 
PropTrie* PropTrie::loadPropertyArea(const char* filename, bool isOwner/* = false*/)
{
    panda::core::SharedMemory* shm = nullptr;
    if (isOwner)
    {
        shm = panda::core::SharedMemory::alloc(filename, PROPERTY_AREA_MAX_SIZE);

        if (shm)
        {
            uint8_t* data = shm->data();
            if (data)
            {   
                PropTrie* pt = new (data)PropTrie(gPropTrieMagic, gPropTrieVersion);
                pt->mShm = shm;
                pt->mData = data + sizeof(PropTrie);
                mPropAreaSize = PROPERTY_AREA_MAX_SIZE;
                mPropAreaDataSize = mPropAreaSize - sizeof(PropTrie);

                return pt;
            }
        }
    }
    else
    {
        shm = panda::core::SharedMemory::attach(filename);

        if (shm)
        {
            uint8_t* const data = shm->data();
            PropTrie* pt = reinterpret_cast<PropTrie*>(data);

            if ((pt->magic() != gPropTrieMagic) || (pt->version() != gPropTrieVersion))
            {
                delete shm;
                return nullptr;
            }

            pt->mShm = shm;
            pt->mData = data + sizeof(PropTrie);
            mPropAreaSize = shm->size();
            mPropAreaDataSize = mPropAreaSize - sizeof(PropTrie);
            return pt;
        }
    }

 
    return nullptr;
}

//static 
void PropTrie::unloadPropertyArea(PropTrie** pa)
{
    if (pa && *pa)
    {
        delete *pa;
        *pa = nullptr;
    }
}


PropTrie::PropTrie(const uint32_t magic, const uint32_t version)
    : mMagicNum(magic), mVersion(version)
{
    atomic_init(&mSerial, 0u);
    memset(mReserved, 0, sizeof(mReserved));
    // Allocate enough space for the root node.
    mBufferUsed = sizeof(TrieNode);
}

PropTrie::~PropTrie()
{
    if (mShm)
    {
        delete mShm;
    }
    /**todo something */
}

const PropNode* PropTrie::find(const char* name)
{
    return findPropertyNode(rootTrieNode(), name, strlen(name), 
                            nullptr, 0, false);
}

bool PropTrie::add(const char* name, unsigned int namelen, 
                   const char* value, unsigned int valuelen)
{
    return findPropertyNode(rootTrieNode(), name, namelen, 
                            value, valuelen, true);
}

bool PropTrie::foreach(void (*propFunc)(const PropNode* pn, void* opaque), 
                      void* opaque)
{
    return foreachProperty(rootTrieNode(), propFunc, opaque);
}

std::atomic_uint_least32_t* PropTrie::serial()
{
    return &mSerial;
}

uint32_t PropTrie::magic() const
{
    return mMagicNum;
}

uint32_t PropTrie::version() const
{
    return mVersion;
}


uint8_t* PropTrie::allocateObj(const size_t size, uint_least32_t* const off)
{
    const size_t alignedSize = PROP_ALIGN(size, sizeof(uint_least32_t));
    if ((mBufferUsed + alignedSize) <= mPropAreaDataSize)
    {
        *off = mBufferUsed;
        mBufferUsed += alignedSize;
        return mData + *off;
    }

    return nullptr;
}

TrieNode* PropTrie::newTrieNode(const char* name, uint32_t namelen, uint_least32_t* const off)
{
    uint_least32_t new_offset;
    uint8_t* const p = allocateObj(sizeof(TrieNode) + namelen + 1, &new_offset);

    if (p != nullptr) 
    {
        TrieNode* tn = new (p) TrieNode;
        tn->name = (char*)(p + sizeof(TrieNode));
        memcpy(tn->name, name, namelen);
        tn->name[namelen] = '\0';
        tn->nameLen = namelen;        

        *off = new_offset;
        return tn;
    }

    return nullptr;
}

PropNode* PropTrie::newPropNode(const char* name, uint32_t namelen,
                                const char* value, uint32_t valuelen, 
                                uint_least32_t* const off)
{
    uint_least32_t new_offset;
    uint8_t* const p = allocateObj(sizeof(PropNode) + namelen + 1, &new_offset);
    if (p != nullptr)
    {
        PropNode* pn;
        if (valuelen <= PROPERTY_VALUE_MAX_SIZE) 
        {
            pn = new (p)PropNode;   
            pn->name = (char*)(p + sizeof(PropNode));
            memcpy(pn->name, name, namelen);
            pn->name[namelen] = '\0';
            memcpy(pn->value, value, valuelen);
            
            *off = new_offset;
            return pn;
        }
    }
                
    return nullptr;   
}

uint8_t* PropTrie::toTrieNodeObj(uint_least32_t off)
{
    if (off <= mPropAreaDataSize)
    {
        return mData + off;
    }
    
    return nullptr;
}

inline TrieNode* PropTrie::toTrieNode(std::atomic_uint_least32_t* off_p)
{
    uint_least32_t off = std::atomic_load_explicit(off_p, std::memory_order_consume);
    return reinterpret_cast<TrieNode*>(toTrieNodeObj(off));
}

inline PropNode* PropTrie::toPropNode(std::atomic_uint_least32_t* off_p)
{
    uint_least32_t off = std::atomic_load_explicit(off_p, std::memory_order_consume);
    return reinterpret_cast<PropNode*>(toTrieNodeObj(off));
}

TrieNode* PropTrie::rootTrieNode()
{
    return reinterpret_cast<TrieNode*>(toTrieNodeObj(0));
}

TrieNode* PropTrie::findTrieNode(TrieNode* const bt, const char* name, 
                                 uint32_t namelen, bool alloc_if_needed)
{
    TrieNode* current = bt;
    while (current)
    {
        const int ret = PropNameCmp(name, namelen, current->name, current->nameLen);
        if (ret == 0) 
        {/** full match node name, mean that we found the target node */
            break;
        }
        else if (ret < 0) 
        {
            uint_least32_t left_offset = std::atomic_load_explicit(&current->left, std::memory_order_relaxed);
            if (left_offset != 0) 
            {
                current = toTrieNode(&current->left);
            }
            else 
            {
                if (!alloc_if_needed) 
                {/** if current node havn't left child node and needn't alloc new one, 
                  * then just break and return nullptr
                  */
                    current = nullptr;
                    break;
                }

                uint_least32_t new_offset;
                TrieNode* tn = newTrieNode(name, namelen, &new_offset);
                if (tn) 
                {
                    std::atomic_store_explicit(&current->left, new_offset, std::memory_order_release);
                }
                current = tn;
                break;
            }
        }
        else 
        {
            uint_least32_t right_offset = std::atomic_load_explicit(&current->right, std::memory_order_relaxed);
            if (right_offset != 0) 
            {
                current = toTrieNode(&current->right);
            }
            else 
            {
                if (!alloc_if_needed) 
                { /** if current node havn't right child node and needn't alloc new one,
                  * then just break and return nullptr
                  */
                    current = nullptr;
                    break;
                }

                uint_least32_t new_offset;
                TrieNode* new_bt = newTrieNode(name, namelen, &new_offset);
                if (new_bt) 
                {
                    std::atomic_store_explicit(&current->right, new_offset, std::memory_order_release);
                }
                current = new_bt;
                break;
            }
        }
    }

    return current;
}

const PropNode* PropTrie::findPropertyNode(TrieNode* const trie, const char* name, 
                                       uint32_t namelen, const char* value, 
                                       uint32_t valuelen, bool alloc_if_needed)
{
    const char* remaining_name = name;
    PropNode* pn = nullptr;
    TrieNode* current = trie;
    while (current) 
    {
        const char* sep = strchr(remaining_name, '.');
        const bool want_subtree = (sep != nullptr);
        const uint32_t substr_size = (want_subtree) ? sep - remaining_name : strlen(remaining_name);

        if (!substr_size) 
        {
            return nullptr;
        }

        TrieNode* root = nullptr;
        uint_least32_t children_offset = std::atomic_load_explicit(&current->children, std::memory_order_relaxed);
        if (children_offset != 0) 
        {
            root = toTrieNode(&current->children);
        }
        else if (alloc_if_needed) 
        {
            uint_least32_t new_offset;
            root = newTrieNode(remaining_name, substr_size, &new_offset);
            if (root) 
            {
                std::atomic_store_explicit(&current->children, new_offset, std::memory_order_release);
            }
        }

        if (!root) 
        {
            return nullptr;
        }

        current = findTrieNode(root, remaining_name, substr_size, alloc_if_needed);
        if (!current) 
        {
            return nullptr;
        }

        if (!want_subtree)
        {
            break;
        }
            

        remaining_name = sep + 1;
    }

    if (current)
    {
         uint_least32_t prop_offset = std::atomic_load_explicit(&current->prop, std::memory_order_relaxed);
        if (prop_offset != 0) 
        {
            return toPropNode(&current->prop);
        }
        else if (alloc_if_needed) 
        {
            uint_least32_t new_offset;
            PropNode* new_info = newPropNode(name, namelen, value, valuelen, &new_offset);
            if (new_info) 
            {
                std::atomic_store_explicit(&current->prop, new_offset, std::memory_order_release);
            }

            return new_info;
        }
    }

    return  nullptr;
}

bool PropTrie::foreachProperty(TrieNode* const trie,
                               void (*propFunc)(const PropNode* pi, void* opaque),
                               void* opaque)
{
    if (trie)
    {
        uint_least32_t left_offset = std::atomic_load_explicit(&trie->left, std::memory_order_relaxed);
        if (left_offset != 0) 
        {
            if (!foreachProperty(toTrieNode(&trie->left), propFunc, opaque))
            {
                return false;
            }
        }

        uint_least32_t prop_offset = std::atomic_load_explicit(&trie->prop, std::memory_order_relaxed);
        if (prop_offset != 0) 
        {
            PropNode* pn = toPropNode(&trie->prop);
            if (pn)
            {
                propFunc(pn, opaque);
            }
            else
            {
                return false;
            }             
        }

        uint_least32_t children_offset = std::atomic_load_explicit(&trie->children, std::memory_order_relaxed);

        if (children_offset != 0) 
        {
            if (!foreachProperty(toTrieNode(&trie->children), propFunc, opaque))
            {
                return false;
            }
        }

        uint_least32_t right_offset = std::atomic_load_explicit(&trie->right, std::memory_order_relaxed);
        if (right_offset != 0)
        {
            if (!foreachProperty(toTrieNode(&trie->right), propFunc, opaque))
            {
                return false;
            }
        }

        return true;
    }

    return false;
}


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

//static 
SystemProperty& SystemProperty::instance()
{
    static SystemProperty sysProp;
    return sysProp;
}

SystemProperty::SystemProperty()
{
    /**default as read only mode, not the owner of property shared memory area */
    reinit(false);   
}

SystemProperty::~SystemProperty()
{
    PropTrie::unloadPropertyArea(&mTrie);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

bool SystemProperty::reinit(bool isOwner)
{
    mTrie = PropTrie::loadPropertyArea(PROPERTY_SHM_NAME, isOwner);

    if (mTrie)
    {
        mIsOwner = isOwner;
        return true;
    }

    return false;
}

bool SystemProperty::isInited()
{
    return mTrie != nullptr;
}


const PropNode* SystemProperty::find(const std::string& key)
{
    if (mTrie)
    {
        return mTrie->find(key.c_str());
    }

    return nullptr;
}

panda::core::PandaErrc SystemProperty::get(const std::string& key, std::string& val)
{
    if (mTrie)
    {
        const PropNode* pn = mTrie->find(key.c_str());
        if (pn)
        {
            val.clear();
            val.append(pn->name, pn->nameLen);
   
            return panda::core::PandaErrc::OK;
        }
        else
        {
            return panda::core::PandaErrc::NotFound;
        }
    }

    return panda::core::PandaErrc::NoInit;
}


panda::core::PandaErrc SystemProperty::add(const std::string& key, const std::string& val)
{
    if (mTrie && mIsOwner)
    {
        if (mTrie->add(key.c_str(), key.size(), val.c_str(), val.size()))
        {
            return panda::core::PandaErrc::OK;
        }
        else
        {
            return panda::core::PandaErrc::Undefined;
        }
    }

    return panda::core::PandaErrc::NoInit;
}

 panda::core::PandaErrc SystemProperty::update(PropNode* pn, const std::string& val)
{
    if (mTrie && mIsOwner)
    {
        if (val.size() < PROPERTY_VALUE_MAX_SIZE)
        {
            // The memcpy call here also races.  Again pretend it
            // used memory_order_relaxed atomics, and use the analogous
            // counterintuitive fence.
            std::atomic_thread_fence(std::memory_order_release);
            memcpy(pn->value, val.c_str(), val.size());
            pn->value[val.size()] = '\0';
            return  panda::core::PandaErrc::OK;
        }
        else
        {
            return panda::core::PandaErrc::OutOfRange;
        }
    }

    return panda::core::PandaErrc::NoInit;
}

 panda::core::PandaErrc SystemProperty::list(PropertyList& proplist)
{
    if (mTrie)
    {
        proplist.clear();
        bool ret = mTrie->foreach([](const PropNode* pn, void* opaque) {
                            PropertyList& pl = *(PropertyList*)(opaque);
                            pl[pn->name] = (char*)pn->value;
                        }, &proplist);

        return ret ? panda::core::PandaErrc::OK : panda::core::PandaErrc::Undefined;
    }

    return panda::core::PandaErrc::NoInit;
}

bool SystemProperty::foreach(void (*propFunc)(const PropNode* pn, void* opaque),
                            void* opaque)
{
    if (mTrie)
    {
        return mTrie->foreach(propFunc, opaque);
    }

    return false;
}


} /** namespace internal_impl */
} /** namespace sys */
} /** namespace panda */
